/*
 * Created by Jaren at 2021/8/17 19:51
 */
import 'dart:typed_data';

import 'package:binary/binary.dart';
import 'package:inmotion_protocol/base/base_message.dart';
import 'package:inmotion_protocol/lorin/constant.dart';
import 'package:inmotion_protocol/lorin/util.dart';
import 'package:inmotion_protocol/util/byte_util.dart';
import 'package:inmotion_protocol/util/log.dart';

class LorinMessage extends IMessage {

    static const int HEAD_BYTE1 = 0xAA;
    static const int HEAD_BYTE2 = HEAD_BYTE1;

    static const int HEAD_BYTE_LENGTH = 2;

    // 消息最少字节数，HEAD + FORMAT + LENGTH = 2+1+1 =4
    static const int MIN_LENGTH = 4;

    // format位在消息中的position
    static const int POSITION_FORMAT = 2;

    // crypt key位在消息中的position（如果存在）
    static const int POSITION_CRYPT_KEY = 3;

    static final Uint8ClampedList HEAD = Uint8ClampedList(2);

    Uint8ClampedList head; // 消息头，2byte
    Uint8 format; // 消息格式
    Uint8? key; // 加密key, 可选项，0-1byte, 根据format的bit3指示是否存在
    Uint8ClampedList? tab; // 包序号，可选项，0-4byte，根据format的bit6指示是否存在
    Uint8 length; // 消息长度，1byte, 从function开始计算
    LorinMessageData data; // 数据段
    Uint8ClampedList? check; // 校验位，可选项，0-4byte，根据format5、format4指示校验类型

    LorinMessage(this.head, this.format, this.key, this.tab, this.length, this.data, this.check) {
        ensureHead();
        head = HEAD;
        genCheck();
    }

    static void ensureHead() {
        if (HEAD[0] != HEAD_BYTE1) {
            HEAD[0] = HEAD_BYTE1;
            HEAD[1] = HEAD_BYTE2;
        }
    }

    static int minLength() {
        return 1;
    }

    static bool hasKey(int format) {
        return (format & Constant.hasEncrypt) == Constant.hasEncrypt;
    }

    static bool hasTab(int format) {
        return (format & Constant.hasTab) == Constant.hasTab;
    }

    static bool hasCheck(int format) {
        return (format & Constant.checkTypeParity) == Constant.checkTypeParity
            || (format & Constant.checkTypeCRC16) == Constant.checkTypeCRC16
            || (format & Constant.checkTypeCRC32) == Constant.checkTypeCRC32;
    }

    void genCheck() {
        if (Util.isEvenParityCheck(format.value)) {
            check = Util.calculateEvenParity(getBytesNeedCheck(this));
        } else if (Util.isCRC16(format.value)) {
            check = Util.crc16(getBytesNeedCheck(this));
        } else if (Util.isCRC32(format.value)) {
            check = Util.crc32(getBytesNeedCheck(this));
        } else {
            check = null;
        }
    }


    int getLengthNeedCheck() {
        return 1 + (hasKey(format.value) ? 1 : 0) +
            (tab != null && hasTab(format.value) ? tab!.length : 0) +
            1 + data.dataLength();
    }

    int getTotalLength() {
        return HEAD_BYTE_LENGTH + getLengthNeedCheck() + Util.calCheckLength(format.value);
    }

    @override
    String genMsgIdentity() {
        StringBuffer identityBuilder = StringBuffer();
        if (hasTab(format.value)) {
            identityBuilder.write(Util.calTab(tab!, 0, tab!.length));
        }
        identityBuilder.write("|");
        if (Util.isSpecialData(format.value)) {
            identityBuilder.write("s");
            identityBuilder.write("|");
            LorinSpecialMessageData msgData = data as LorinSpecialMessageData;
            Uint8ClampedList cmd = Uint8ClampedList(msgData.cmd.length);
            ByteUtil.copy(msgData.cmd, 0, cmd, 0, cmd.length);
            // 屏蔽消息来源（CMD首字节bit7表示消息来源）
            cmd[0] = (cmd[0] & 0x0F);
            identityBuilder.write(ByteUtil.bytes2IntLe(cmd, 0, cmd.length));
        } else if (Util.isGeneralData(format.value)) {
            identityBuilder.write("g");
            identityBuilder.write("|");
            LorinGeneralMessageData msgData = data as LorinGeneralMessageData;
            // &0x0F来屏蔽消息来源（channel bit7表示消息来源）
            identityBuilder.write(msgData.channel.value & 0x0F);
            identityBuilder.write("|");
            identityBuilder.write(msgData.property.value);
        }
        return identityBuilder
            .toString()
            .hashCode
            .toString();
    }

    @override
    String toString() {
        return "LorinMessage{"
            "format: 0x" + ByteUtil.byte2Hex(format.value) +
            "(" + formatString() + ")" +
            (key == null ? '' : ", key: 0x" + ByteUtil.byte2Hex(key!.value)) +
            (tab == null ? '' : ", tab: 0x" + ByteUtil.bytes2HexString(tab!)) +
            ", length: 0x" + ByteUtil.byte2Hex(length.value) +
            ", data: $data " +
            ", check: 0x" + ByteUtil.bytes2HexString(check!) +
            "}";
    }

    static Uint8ClampedList getBytesNeedCheck(LorinMessage msg) {
        Uint8ClampedList bytesNeedCheck = Uint8ClampedList(msg.getLengthNeedCheck());
        int index = 0;
        bytesNeedCheck[index++] = msg.format.value;
        if (hasKey(msg.format.value)) {
            bytesNeedCheck[index++] = msg.key!.value;
        }
        if (msg.tab != null && hasTab(msg.format.value)) {
            ByteUtil.copy(msg.tab!, 0, bytesNeedCheck, index, msg.tab!.length);
            index += msg.tab!.length;
        }
        bytesNeedCheck[index++] = msg.length.value;

        ByteUtil.copy(msg.data.toBytes(), 0, bytesNeedCheck, index, msg.data.dataLength());

        return bytesNeedCheck;
    }

    static int getBytesNeedCheckLength(Uint8ClampedList bytes, int offset) {
        if (bytes.length - offset < MIN_LENGTH) {
            LogUtils.e("getBytesNeedCheckLength", Exception("Error length of bytes."));
            return 0;
        }

        int lengthNeedCheck = 0;

        int format = bytes[offset + POSITION_FORMAT];
        lengthNeedCheck++; // format, 1 byte
        if (hasKey(format)) {
            lengthNeedCheck++;
        }

        int tabLength = 0;
        if (hasTab(format)) {
            tabLength = Util.calTabLength(bytes, offset + POSITION_FORMAT + 1 + (hasKey(format) ? 1 : 0));
        }

        lengthNeedCheck += tabLength;

        int dataLength = bytes[offset + POSITION_FORMAT + 1 + (hasKey(format) ? 1 : 0) + tabLength] & 0x0FF;

        lengthNeedCheck++; // length, 1 byte

        if (dataLength > 0) {
            lengthNeedCheck += dataLength;
        }

        return lengthNeedCheck;
    }

    String formatString() {
        StringBuffer sb = StringBuffer();
        if (hasTab(format.value)) {
            sb.write("有TAB|");
        } else {
            sb.write("无TAB|");
        }

        if (Util.isCRC32(format.value)) {
            sb.write("CRC32校验|");
        } else if (Util.isCRC16(format.value)) {
            sb.write("CRC16校验|");
        } else if (Util.isEvenParityCheck(format.value)) {
            sb.write("奇偶校验|");
        } else {
            sb.write("无校验|");
        }

        if (hasKey(format.value)) {
            sb.write("加密|");
        } else {
            sb.write("不加密|");
        }

        if (Util.isSpecialMany2ManyData(format.value)) {
            sb.write("专用组-多主多从");
        } else if (Util.isSpecialOne2ManyData(format.value)) {
            sb.write("专用组-一主多从");
        } else if (Util.isSpecialOne2OneData(format.value)) {
            sb.write("专用组-一主一从");
        } else if (Util.isGeneralMany2ManyData(format.value)) {
            sb.write("标准组-多主多从");
        } else if (Util.isGeneralOne2ManyData(format.value)) {
            sb.write("标准组-一主多从");
        } else if (Util.isGeneralOne2OneData(format.value)) {
            sb.write("标准组-一主一从");
        } else {
            sb.write("未知格式");
        }

        return sb.toString();
    }
}

abstract class LorinMessageData {
    int dataLength();

    Uint8ClampedList toBytes();

    void from(int dataType, Uint8ClampedList bytes, int offset);

    static int minLength() {
        return 0;
    }
}

class LorinGeneralMessageData extends LorinMessageData {

    Uint8? source; // 来源设备
    Uint8? target; // 目标设备
    Uint8 channel; // 通道，最高位表示数据帧方向，主机发向从机最高位置 0，从机应答主机最高位置 1
    Uint8 property; // 属性，最高位表示对该属性的读写操作，主机发出时用 0 表示读，1 表示写，从机应 答时用 0 表示对读的应答，1 表示对写的应答
    Uint8ClampedList? para; // 参数

    LorinGeneralMessageData(this.source, this.target, this.channel, this.property, this.para);


    @override
    int dataLength() {
        return (target == null ? 0 : 1)
            +
            (source == null ? 0 : 1)
            +
            1 + 1 + (para == null ? 0 : para!.length);
    }

    static int minLength() {
        return 2;
    }

    @override
    void from(int dataType, Uint8ClampedList bytes, int offset) {
        if (bytes.length < minLength()) {
            LogUtils.w("from: Invalid data.", Exception("Error length " + bytes.length.toString() + " of data with offset[" + offset.toString() + "]"));
            return;
        }
        if (dataType == Constant.dataTypeGeneralMany2Many) {
            // 多对多
            target = ByteUtil.toUint8(bytes[offset]);
            offset++;
            source = ByteUtil.toUint8(bytes[offset]);
            offset++;
        } else if (dataType == Constant.dataTypeGeneralOne2Many) {
            // 一对多
            target = ByteUtil.toUint8(bytes[offset]);
            offset++;
        }

        channel = ByteUtil.toUint8(bytes[offset]);
        offset++;
        property = ByteUtil.toUint8(bytes[offset]);
        offset++;
        if (bytes.length > offset) {
            para = Uint8ClampedList(bytes.length - offset);
            ByteUtil.copy(bytes, offset, para!, 0, para!.length);
        }
    }

    static LorinGeneralMessageData? sFrom(int dataType, Uint8ClampedList bytes, int offset) {
        if (bytes.length < minLength()) {
            LogUtils.w("from: Invalid data.", Exception("Error length " + bytes.length.toString() + " of data with offset[" + offset.toString() + "]"));
            return null;
        }
        Uint8? target;
        Uint8? source;
        if (dataType == Constant.dataTypeGeneralMany2Many) {
            // 多对多
            source = ByteUtil.toUint8(bytes[offset]);
            offset++;
            target = ByteUtil.toUint8(bytes[offset]);
            offset++;
        } else if (dataType == Constant.dataTypeGeneralOne2Many) {
            // 一对多
            target = ByteUtil.toUint8(bytes[offset]);
            offset++;
        }

        Uint8 channel = ByteUtil.toUint8(bytes[offset]);
        offset++;
        Uint8 property = ByteUtil.toUint8(bytes[offset]);
        offset++;
        Uint8ClampedList? para;
        if (bytes.length > offset) {
            para = Uint8ClampedList(bytes.length - offset);
            ByteUtil.copy(bytes, offset, para, 0, para.length);
        }
        return LorinGeneralMessageData(source, target, channel, property, para);
    }

    @override
    Uint8ClampedList toBytes() {
        int totalLength = dataLength();
        Uint8ClampedList bytes = Uint8ClampedList(totalLength);
        int index = 0;
        if (source != null) {
            bytes[index] = source!.value;
            index++;
        }
        if (target != null) {
            bytes[index] = target!.value;
            index++;
        }
        bytes[index] = channel.value;
        index++;

        bytes[index] = property.value;
        index++;

        if (para != null && para!.isNotEmpty) {
            ByteUtil.copy(para!, 0, bytes, index, para!.length);
        }
        return bytes;
    }


    @override
    String toString() {
        return 'LorinGeneralMessageData{' +
            (source == null ? '' : "source: " + ByteUtil.byte2Hex(source!.value) + ", ") +
            (target == null ? '' : "target: " + ByteUtil.byte2Hex(target!.value) + ", ") +
            "channel: " + ByteUtil.byte2Hex(channel.value) +
            ", property: " + ByteUtil.byte2Hex(property.value) +
            ", para: " + (para == null ? 'null' : ByteUtil.bytes2HexString(para!)) +
            "}";
    }
}

class LorinSpecialMessageData extends LorinMessageData {

    Uint8? source; // 来源设备
    Uint8? target; // 目标设备
    /*
     * 第一个字节的最高位 bit7 表示数据帧方向，主机发向从机最高位置 0，从机 应答主机最高位置 1（从机将收到的通道最高位置 1 回复给主机即可）
     * bit6 用于表示读写属性，0 表示读，1 表示写
     * bit5 用于表示 cmd 位数扩展，bit5 为 1 表示 Cmd 扩展一个字节
     */
    Uint8ClampedList cmd; // 通道，长度1-4，
    Uint8ClampedList? para; // 参数，长度0-254

    LorinSpecialMessageData(this.source, this.target, this.cmd, this.para);

    @override
    int dataLength() {
        return (target == null ? 0 : 1)
            +
            (source == null ? 0 : 1)
            +
            cmd.length + (para == null ? 0 : para!.length);
    }

    static int minLength() {
        return 1;
    }

    @override
    void from(int dataType, Uint8ClampedList bytes, int offset) {
        if (dataType == Constant.dataTypeSpecialMany2Many) {
            source = ByteUtil.toUint8(bytes[offset]);
            offset++;
            target = ByteUtil.toUint8(bytes[offset]);
            offset++;
        } else if (dataType == Constant.dataTypeSpecialOne2Many) {
            target = ByteUtil.toUint8(bytes[offset]);
            offset++;
        }
        int cmdLength = Util.calCmdLength(bytes, offset);
        cmd = Uint8ClampedList(cmdLength);
        ByteUtil.copy(bytes, offset, cmd, 0, cmdLength);
        int paraLength = bytes.length - offset - cmdLength;
        offset += cmdLength;
        if (paraLength > 0) {
            para = Uint8ClampedList(paraLength);
            ByteUtil.copy(bytes, offset, para!, 0, paraLength);
        }
    }

    @override
    Uint8ClampedList toBytes() {
        Uint8ClampedList bytes = Uint8ClampedList(dataLength());
        int index = 0;
        if (source != null) {
            bytes[index] = source!.value;
            index++;
        }
        if (target != null) {
            bytes[index] = target!.value;
            index++;
        }
        if (cmd.isNotEmpty) {
            ByteUtil.copy(cmd, 0, bytes, index, cmd.length);
            index += cmd.length;
        }
        if (para != null && para!.isNotEmpty) {
            ByteUtil.copy(para!, 0, bytes, index, para!.length);
        }
        return bytes;
    }

    static LorinSpecialMessageData? sFrom(int dataType, Uint8ClampedList bytes, int offset) {
        if (bytes.length < minLength()) {
            LogUtils.e("from: Invalid data.", Exception("Error length " + bytes.length.toString() + " of data with offset[" + offset.toString() + "]"));
            return null;
        }
        Uint8? target;
        Uint8? source;
        if (dataType == Constant.dataTypeSpecialMany2Many) {
            source = ByteUtil.toUint8(bytes[offset]);
            offset++;
            target = ByteUtil.toUint8(bytes[offset]);
            offset++;
        } else if (dataType == Constant.dataTypeSpecialOne2Many) {
            target = ByteUtil.toUint8(bytes[offset]);
            offset++;
        }
        int cmdLength = Util.calCmdLength(bytes, offset);
        Uint8ClampedList cmd = Uint8ClampedList(cmdLength);
        ByteUtil.copy(bytes, offset, cmd, 0, cmdLength);
        int paraLength = bytes.length - offset - cmdLength;
        offset += cmdLength;
        Uint8ClampedList? para;
        if (paraLength > 0) {
            para = Uint8ClampedList(paraLength);
            ByteUtil.copy(bytes, offset, para, 0, paraLength);
        }

        return LorinSpecialMessageData(source, target, cmd, para);
    }


    @override
    String toString() {
        return 'LorinSpecialMessageData{' +
            (source == null ? '' : "source: " + ByteUtil.byte2Hex(source!.value) + ", ") +
            (target == null ? '' : ", target: " + ByteUtil.byte2Hex(target!.value) + ", ") +
            "cmd: " + ByteUtil.bytes2HexString(cmd) +
            ", para: " + (para == null ? 'null' : ByteUtil.bytes2HexString(para!)) +
            "}";
    }
}

class LorinUnknownMessageData extends LorinMessageData {

    @override
    int dataLength() {
        return 0;
    }

    int minLength() {
        return 0;
    }

    @override
    void from(int dataType, Uint8ClampedList bytes, int offset) {

    }

    @override
    Uint8ClampedList toBytes() {
        return Uint8ClampedList(0);
    }
}